/// File: Robotics4.NET\BodyMap\RobletBroker.cs
/// 
/// ------------------------------------------------------------
/// Copyright (c) 2004
///   Antonio Cisternino (cisterni.unipi.it),
///   Diego Colombo      (colombod.unipi.it)
/// 
/// The use and distribution terms for this software are 
/// contained in the file named license.txt, which can be found 
/// in the root of this distribution.
/// By using this software in any fashion, you are agreeing to 
/// be bound by the terms of this license.
///
/// You must not remove this notice, or any other, from this
/// software.
/// ------------------------------------------------------------

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System.Collections;
using System.Collections.Specialized;
#if WIN
using System.Collections.Generic;
using R4NRobletsList = System.Collections.Generic.List<Robotics4.NET.RobletBase>;
using R4nDomainList = System.Collections.Generic.List<System.AppDomain>;
using R4NConfigurationSettings = System.Configuration.ConfigurationSettings;
using R4NThread = System.Threading.Thread;
#elif CF
using R4NConfigurationSettings = OpenNETCF.Configuration.ConfigurationSettings;
using R4NThread = OpenNETCF.Threading.ThreadEx;
#endif

namespace Robotics4.NET
{
    /// <summary>
    /// RobletBroker is a class that model a sort of router that route messages 
    /// from in-process roblets towards the body map and outside friends. The
    /// class implements a singleton pattern: all members are static.
    /// </summary>
    public class RobletBroker : MarshalByRefObject
    {
        internal static RobletBroker Broker;

        internal static XMLSerializerPool SerializerPool = new XMLSerializerPool();

        /// <summary>
        /// This class is responsible for sending messages via UDP packets.
        /// </summary>
        internal class MessageManager : MarshalByRefObject
        {
            /// <summary>
            /// An UDP client representing the bodymap. This is setup with the
            /// information coming from the configuration file.
            /// </summary>
            private UdpClient bodymap;
            /// <summary>
            /// Default hostname for the bodymap (If not specified otherwise)
            /// </summary>
            private const string DefaultHostname = "localhost";
            /// <summary>
            /// Default UDP port for the bodymap (If not specified otherwise)
            /// </summary>
            private const int DefaultPort = 6666;
            /// <summary>
            /// Host used to communicate with body map
            /// </summary>
            internal string host;

            internal string Host
            {
                get { return host; }
            }
            /// <summary>
            /// UDP port used to communicate with body map
            /// </summary>
            internal int port;

            internal int Port
            {
                get { return port; }
            }
            /// <summary>
            /// Local UDP port used to listen for incoming messages.
            /// </summary>
            internal int localPort;

            internal int LocalPort
            {
                get { return localPort; }
            }
            /// <summary>
            /// Address of the interface that should receive boymap replies.
            /// </summary>
            internal string brokerInterface;

            /// <summary>
            /// Build an instance of MessageManager assuming the default hostname and
            /// port for bodymap.
            /// </summary>
            /// <param name="port">Local UDP port to be used for incoming messages</param>
            public MessageManager(int port)
                : this(port, DefaultHostname, DefaultPort, null)
            {
                brokerInterface = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()).AddressList[0].ToString();
            }

            /// <summary>
            /// Build an instance of MessageManager.
            /// </summary>
            /// <param name="localport">Local UDP port to be used for incoming messages</param>
            /// <param name="host">Remote host running the bodymap</param>
            /// <param name="port">UDP port used to contact the bodymap</param>
            /// <param name="brokerInterface">Address of the interface that should receive boymap replies.</param>
            public MessageManager(int localport, string host, int port, string brokerInterface)
            {
                localPort = localport;
                bodymap = new UdpClient(host, port);
                this.brokerInterface = brokerInterface;
            }

            internal void SendBodyMap(byte[] d, int len)
            {
                bodymap.Send(d, len);
            }

        }


        /// <summary>
        /// 
        /// </summary>
        protected R4NRobletsList RunningRoblets;

        /// <summary>
        /// 
        /// </summary>
        protected R4nDomainList RunningRobletsDomains;

        /// <summary>
        /// 
        /// </summary>
        public int Port
        {
            get
            {
                return Dispatcher.Port;
            }
            set
            {
                Dispatcher.port = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public int LocalPort
        {
            get
            {
                return Dispatcher.LocalPort;
            }
            set
            {
                Dispatcher.localPort = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public string Host
        {
            get
            {
                return Dispatcher.Host;
            }
            set
            {
                Dispatcher.host = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        internal MessageManager dispatcher;

        internal MessageManager Dispatcher
        {
            get { return dispatcher; }
        }
        /// <summary>
        /// 
        /// </summary>
        internal R4NThread Listener;

        /// <summary>
        /// 
        /// </summary>
        internal RobletBroker()
        {
            UpdateSettings();
            RunningRoblets = new R4NRobletsList();
            RunningRobletsDomains = new R4nDomainList();
            Listener = null;
        }

        /// <summary>
        /// Reloads application's settings
        /// </summary>
        public void UpdateSettings()
        {
            NameValueCollection netconfig = R4NConfigurationSettings.GetConfig("Roblets_Workspace/Network") as NameValueCollection;

            int port = 6666;
            string host = "localhost";
            int localport = 6667;
            string brokernwif = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()).AddressList[0].ToString();
            if (netconfig != null && netconfig["Port"] != null) port = int.Parse(netconfig["Port"] as string);
            if (netconfig != null && netconfig["LocalPort"] != null) localport = int.Parse(netconfig["LocalPort"] as string);
            if (netconfig != null && netconfig["BodyMapHost"] != null) host = netconfig["BodyMapHost"] as string;
            if (netconfig != null && netconfig["BrokerInterface"] != null) brokernwif = netconfig["BrokerInterface"] as string;

            dispatcher = new MessageManager(localport, host, port, brokernwif);

            running = true;


            if (Listener != null) Listener.Abort();
            Listener = new R4NThread(new ThreadStart(MessageListener));
            Listener.Name = "MessageListener";
            Listener.IsBackground = true;
            Listener.Priority = ThreadPriority.AboveNormal;
            Listener.Start();

        }

        /// <summary>
        /// 
        /// </summary>
        protected bool running;

        /// <summary>
        /// 
        /// </summary>
        protected void MessageListener()
        {
            XmlDataReceiver rcv = new XmlDataReceiver(new IPEndPoint(IPAddress.Any, Dispatcher.LocalPort), new UdpClient(Dispatcher.LocalPort));
            while (running)
            {
                string xmldata = rcv.ReceiveMessage();
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(xmldata);
                if (doc.ChildNodes[1].Name == "MetaDataRequest")
                {
                    RobletBase roblet = RunningRoblets[int.Parse(doc.SelectSingleNode("/*/RobletID").InnerText)];
                    roblet.SendInterface();
                }
                else if (doc.ChildNodes[1].Name == "RestartRoblet")
                {
                    int id = int.Parse(doc.SelectSingleNode("/*/RobletID").InnerText);
                    if (!(RunningRoblets[id]).Thread.IsAlive)
                    {
                        RobletBase roblet = (RobletBase)Activator.CreateInstance(RunningRoblets[id].GetType());
                        roblet.ID = id;
                        RunningRoblets[id] = roblet;
                    }
                }
                else
                {
                    // deliver the message to the box or launch interrupt
                    int id = int.Parse(doc.SelectSingleNode("/*/RobletID").InnerText);
                    if (RunningRoblets.Count > id) // For robustness!
                    {
                        RobletBase roblet = RunningRoblets[id];
                        roblet.DeliverSignal(doc.ChildNodes[1]);
                    }
                }
            }
        }

        private void StartRoblets()
        {
            NameValueCollection agtconfig = R4NConfigurationSettings.GetConfig("Roblets_Workspace/Roblets") as NameValueCollection;
            foreach (string key in agtconfig.Keys)
            {
                if (key.ToLower().StartsWith("roblet"))
                {
                    string name = null;
                    if (key.IndexOf('#') != -1)
                    { // A name has been given in the configuration file.
                        name = key.Substring(key.IndexOf('#') + 1);
                    }
                    string[] s = (agtconfig[key] as string).Split(',');
                    ActivateRoblet(name, s[1], s[0]);
                }
            }
        }

        /// <summary>
        /// Initialize a Roblet in another application domain
        /// </summary>
        /// <param name="name">Name of the Roblet</param>
        /// <param name="assembly">Assembly Name</param>
        /// <param name="typename">Name of the Roblet Type</param>
        private void ActivateRoblet(string name, string assembly, string typename)
		{
            AppDomainSetup setup = new AppDomainSetup();
            setup.ApplicationTrust = AppDomain.CurrentDomain.ApplicationTrust;
            setup.ShadowCopyFiles = "true";
            AppDomain Domain = AppDomain.CreateDomain(assembly, null,setup);

            RobletBase roblet = (RobletBase)Domain.CreateInstanceAndUnwrap(assembly, typename);

			int ID = RunningRoblets.Count;
            RunningRoblets.Add(roblet);
            RunningRobletsDomains.Add(Domain);
            // Start the roblet
            roblet.Init(name, ID, Broker);
		}

        /// <summary>
        /// Send a message to a friend roblet. Direct communication among roblets should be
        /// kept to minimum. The more roblets communicate directly, the less control the
        /// bodymap has over peripheral roblets.
        /// </summary>
        /// <param name="id">Id of the roblet target of the communication</param>
        /// <param name="broker">Broker host of the process hosting the roblet</param>
        /// <param name="port">UDP port of the broker</param>
        /// <param name="msg">Message to be sent</param>
        /// <param name="sender">Sender of the message</param>
        /// <param name="external">Types required to ensure a correct XML serialization</param>
        internal static void SendState(int id, string broker, int port, RobletMessage msg, object sender, Type[] external, MemoryStream MemStream)
        {
            lock (MemStream)
            {
                string host = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()).AddressList[0].ToString();
                msg.BrokerHost = broker;
                msg.BrokerPort = port;
                msg.RobletID = id;

                if (SerializerPool[msg.GetType(), msg.Name] == null)
                    SerializerPool[msg.GetType(), msg.Name] = external == null ? new XmlSerializer(msg.GetType()) : new XmlSerializer(msg.GetType(), external);

                SerializerPool[msg.GetType(), msg.Name].Serialize(MemStream, msg);

                byte[] data = MemStream.GetBuffer();
                if (broker == host && port == Broker.Dispatcher.LocalPort)
                {
                    string xmldata = System.Text.Encoding.UTF8.GetString(data, 0, (int)MemStream.Length);
                    XmlDocument doc = new XmlDocument();
                    doc.LoadXml(xmldata);
                    Broker.RunningRoblets[id].DeliverSignal(doc.ChildNodes[1]);
                }
                else
                {
                    (new UdpClient(broker, port)).Send(data, (int)MemStream.Length);
                }

                MemStream.Position = 0;
                MemStream.SetLength(0);
            }
        }

        /// <summary>
        /// Send a message to the bodymap.
        /// </summary>
        /// <param name="msg">Message to be sent</param>
        /// <param name="sender">Sender of the message</param>
        /// <param name="external">Types required to ensure a correct XML serialization</param>
        internal static void SendState(RobletMessage msg, object sender, Type[] external, MemoryStream MemStream)
        {
            lock (MemStream)
            {
                msg.BrokerHost = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()).AddressList[0].ToString();
                msg.BrokerPort = Broker.Dispatcher.LocalPort;

                if (SerializerPool[msg.GetType(), msg.Name] == null)
                    SerializerPool[msg.GetType(), msg.Name] = external == null ? new XmlSerializer(msg.GetType()) : new XmlSerializer(msg.GetType(), external);

                SerializerPool[msg.GetType(), msg.Name].Serialize(MemStream, msg);
                byte[] data = MemStream.GetBuffer();
                Broker.Dispatcher.SendBodyMap(data, (int)MemStream.Length);

                MemStream.Position = 0;
                MemStream.SetLength(0);
            }
        }

        
        private void StopRoblets()
        {
            foreach (RobletBase roblet in RunningRoblets)
            {
                roblet.Thread.Abort();
            }
            RunningRoblets.Clear();
            foreach (AppDomain ap in RunningRobletsDomains)
                AppDomain.Unload(ap);
            RunningRobletsDomains.Clear();
        }

        private void RestartRoblets()
        {
            Stop();
            UpdateSettings();
            Start();
        }

        /// <summary>
        /// Initialize the broker without starting Roblets.
        /// If you just need to start the broker you can call directly the
        /// Start method.
        /// </summary>
        public static void Init()
        {
            Broker = new RobletBroker();
        }

        /// <summary>
        /// Starts Roblets as described in configuration File
        /// </summary>
        public static void Start()
        {
            if (Broker == null)
                Broker = new RobletBroker();
            Broker.StartRoblets();
        }

        /// <summary>
        /// Stops all running roblets
        /// </summary>
        public static void Stop()
        {
            Broker.StopRoblets();
        }

        /// <summary>
        /// Reloads configuration and restarts roblets
        /// </summary>
        public static void Restart()
        {
            Broker.RestartRoblets();
        }
    }

}
